package sql.parser;

import sql.lexer.SQLLexer;
import sql.utils.Utils;

/**
 * sql  ->  select columns  from  table where conditions sort
 * 	        select columns from table sort
 *
 * columns ->  *
 *             col, col, col….
 *
 * col ->  name asName1
 * 	       (sql) asName1
 * 	       name
 * 	       func(params) asName1
 *
 * asName ->  as name
 * 		     null
 *
 * params -> param, param2,param3….
 *
 * table ->  tablename
 * 	         tablename as tablename1
 *
 * conditions-> expr
 * 		       col = (sql)
 *             col in incond
 *
 * incond -> (sql)
 *           (element,element...)
 *
 * sort -> orderby
 *         orderby limit
 *         limit
 *         null
 *
 * orderby -> col [asc|desc]
 *            orderbyTail
 *
 * orderbyTail -> ,col ["asc"|"desc"] orderByTail
 *                null
 *
 * limit -> "limit" limitValue
 *
 * expr ->  or
 * Or      ->    And OrTail
 * OrTail  ->    || And OrTail
 *                null
 *
 * And      ->    BracketOrItems AndTail
 * AndTail  ->    &&  BracketOrItems AndTail
 *                       null
 *
 * bracketOrItems    ->    (Or)
 *                         Items
 * items -> left op right
 *
 * right -> value
 *          (sql)
 */
public class SelectParser extends SyntaxJudge {

    public SelectParser(SQLLexer lexer) {
        super(lexer);
    }


    public SyntaxNode parse() {
        SyntaxNode sqlNode = new SyntaxNode(SyntaxNodeType.Sql, null);
        this.nextToken();
        if(sql(sqlNode) && isEnd()) {
            return sqlNode;
        } else {
           throw parseException();
        }
    }

    protected RuntimeException parseException() {
        throw new RuntimeException("编译出错，token:" + getStrToken());
    }


    protected boolean sql(SyntaxNode sqlNode) {
        if(this.getSyntaxType() == SyntaxType.Select) {
            SyntaxNode selectNode = new SyntaxNode(SyntaxNodeType.Select, null);
            sqlNode.setSelectNode(selectNode);
            nextToken();
            if(!columns(selectNode)) {
                throw parseException();
            } else {
                if(!from(sqlNode)) {
                    throw parseException();
                } else if(isFromEnd()) {
                    return true;
                } else if(!where()) {
                    throw parseException();
                } else if(!conditions(sqlNode)) {
                    return false;
                } else if(!sort(sqlNode)) {
                    return false;
                } else {
                    return true;
                }
            }
        } else {
            return false;
        }
    }

    private boolean sort(SyntaxNode sqlNode) {
        if(isEnd()) {
            return true;
        } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
          return true;
        } else if(!orderBy(sqlNode)) {
            throw parseException();
        } else if(!limit(sqlNode)) {
            throw parseException();
        } else {
            return true;
        }
    }

    private boolean orderBy(SyntaxNode sqlNode) {
        if(this.getSyntaxType() == SyntaxType.Order) {
            nextToken();
            if(this.getSyntaxType() == SyntaxType.By) {
                this.nextToken();
                SyntaxNode orderByNode = new SyntaxNode(SyntaxNodeType.OrderBy);
                sqlNode.setOrderByNode(orderByNode);

                SyntaxNode orderByPairNode = new SyntaxNode(SyntaxNodeType.OrderByPair);
                orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.Col, this.getStrToken()));
                this.nextToken();

                SyntaxType syntaxType = this.getSyntaxType();
                if (syntaxType == SyntaxType.OrderByAsc) {
                    orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.OrderByAsc, this.getStrToken()));
                } else if (syntaxType == SyntaxType.OrderByDesc) {
                    orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.OrderByDesc, this.getStrToken()));
                } else {
                    throw parseException();
                }

                orderByNode.addChild(orderByPairNode);

                this.nextToken();
                return orderByTail(orderByNode);
            } else {
                throw parseException();
            }
        } else if(this.getSyntaxType() == SyntaxType.limit) {
            return true;
        } else {
            throw parseException();
        }
    }

    private boolean orderByTail(SyntaxNode orderByNode) {
        SyntaxType syntaxType = this.getSyntaxType();
        if(this.isEnd()) {
            return true;
        } else if(syntaxType == SyntaxType.limit) {
            return true;
        } else if(syntaxType == SyntaxType.Comma) {
            nextToken();

            SyntaxNode orderByPairNode = new SyntaxNode(SyntaxNodeType.OrderByPair);
            orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.Col, this.getStrToken() ));
            this.nextToken();

            syntaxType = this.getSyntaxType();
            if( syntaxType == SyntaxType.OrderByAsc ) {
                orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.OrderByAsc, this.getStrToken()));
            } else if( syntaxType == SyntaxType.OrderByDesc) {
                orderByPairNode.addChild(new SyntaxNode(SyntaxNodeType.OrderByDesc, this.getStrToken()));
            } else {
                throw parseException();
            }

            orderByNode.addChild(orderByPairNode);

            this.nextToken();

            return orderByTail(orderByNode);
        } else {
            throw parseException();
        }
    }

    private boolean limit(SyntaxNode sqlNode) {
        if(this.isEnd()) {
            return true;
        } else if (this.getSyntaxType() == SyntaxType.limit) {
            this.nextToken();
            SyntaxNode limitNode = new SyntaxNode(SyntaxNodeType.Limit, this.getStrToken());
            sqlNode.setLimitNode(limitNode);
            this.nextToken();
            return true;
        } else {
            throw parseException();
        }
    }

    private boolean where() {
        if(this.getSyntaxType() == SyntaxType.Where) {
            nextToken();
            return true;
        } else if(isSort()) {
            return true;
        }
        else {
            throw parseException();
        }
    }


    private boolean columns(SyntaxNode selectNode) {
        if(!column(selectNode)) {
            throw parseException();
        } else {
            return columnsTail(selectNode);
        }
    }

    private boolean columnsTail(SyntaxNode selectNode) {
        if(this.getSyntaxType() == SyntaxType.From) {
            return true;
        } else if(this.getSyntaxType() == SyntaxType.Comma) {
            nextToken();
            if(!column(selectNode)) {
                throw parseException();
            } else {
                return columnsTail(selectNode);
            }
        } else {
            throw parseException();
        }
    }

    private boolean func(SyntaxNode funcNode) {
        if(this.getSyntaxType() == SyntaxType.BracketStart) {
            nextToken();
            if(!params(funcNode)) {
                return false;
            } else {
                if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                    nextToken();
                    return true;
                } else {
                    return false;
                }
            }
        } else {
            return false;
        }
    }

    private boolean params(SyntaxNode funcNode) {
        if(isElement(this.getSyntaxType())) {
            SyntaxNode syntaxNode = new SyntaxNode(SyntaxNodeType.FuncParam, this.getStrToken());
            funcNode.addChild(syntaxNode);

            nextToken();
            if(this.getSyntaxType() == SyntaxType.Comma) {
                nextToken();
                return paramsTail(funcNode);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private boolean paramsTail(SyntaxNode funcNode) {
        if(isElement(this.getSyntaxType())) {
            SyntaxNode syntaxNode = new SyntaxNode(SyntaxNodeType.FuncParam, this.getStrToken());
            funcNode.addChild(syntaxNode);
            nextToken();
            if(this.getSyntaxType() == SyntaxType.Comma) {
                nextToken();
                return paramsTail(funcNode);
            } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private boolean column(SyntaxNode selectNode) {
        if(isElement(this.getSyntaxType())) {
            if(this.getSyntaxType() == SyntaxType.Star) {
                SyntaxNode colNode = new SyntaxNode(SyntaxNodeType.Col, "*");
                selectNode.addChild(colNode);
                nextToken();
                return true;
            } else {
                String token = this.getStrToken();
                SyntaxNode colNode;
                nextToken();
                if(this.getSyntaxType() == SyntaxType.BracketStart) {
                    //函数调用
                    colNode = new SyntaxNode(SyntaxNodeType.Func, token);
                    if(!func(colNode)) {
                        return false;
                    }
                } else {
                     colNode = new SyntaxNode(SyntaxNodeType.Col, token);
                }
                selectNode.addChild(colNode);
                return aliasForColumn(colNode);
            }
        } else if(this.getSyntaxType() == SyntaxType.BracketStart){
            nextToken();
            SyntaxNode sqlNode = new SyntaxNode(SyntaxNodeType.Col, null);
            selectNode.addChild(sqlNode);
            if(!sql(sqlNode)) {
                return false;
            } else {
                if(this.getSyntaxType() != SyntaxType.BracketEnd) {
                    return false;
                } else {
                    nextToken();
                    if(this.getSyntaxType() != SyntaxType.As) {
                        return false;
                    } else {
                        return aliasForColumn(sqlNode);
                    }
                }
            }
        } else {
            return false;
        }
    }

    private boolean aliasForColumn(SyntaxNode colNode) {
        if(this.getSyntaxType() == SyntaxType.Comma) {
            return true;
        } else if(this.getSyntaxType() == SyntaxType.From) {
            return true;
        } else {
            if(this.getSyntaxType() == SyntaxType.As) {
                nextToken();
                if(this.getSyntaxType() != SyntaxType.Identifier) {
                    return false;
                } else {
                    colNode.setAlias(this.getStrToken());
                    nextToken();
                    return true;
                }
            } else {
                return false;
            }
        }
    }

    private boolean from(SyntaxNode sqlNode) {
        if(this.getSyntaxType() == SyntaxType.From) {
            nextToken();
            if(this.getSyntaxType() != SyntaxType.Identifier) {
                throw parseException();
            } else {
                SyntaxNode fromNode = new SyntaxNode(SyntaxNodeType.From, this.getStrToken());
                sqlNode.setFromNode(fromNode);
                nextToken();
                if(this.getSyntaxType() == SyntaxType.Where) {
                    return true;
                } else if(this.getSyntaxType() == SyntaxType.As) {
                    nextToken();
                    if(this.getSyntaxType() != SyntaxType.Identifier) {
                        throw parseException();
                    } else {
                        fromNode.setAlias(this.getStrToken());
                        nextToken();
                        return true;
                    }
                } else {
                    return true;
                }
            }
        } else {
            throw parseException();
        }
    }

    private boolean isFromEnd() {
        //整个SQL语句结束
        if(this.isEnd()) {
            return true;
        } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isSort() {
        return this.getSyntaxType() == SyntaxType.Order || this.getSyntaxType() == SyntaxType.limit;
    }

    //<editor-fold desc="条件相关">
    protected boolean conditions(SyntaxNode sqlNode) {
        if(isSort()) {
            return true;
        }
        SyntaxNode conditionsNode = new SyntaxNode(SyntaxNodeType.Conditions);
        sqlNode.setWhereNode(conditionsNode);
        if(!or(conditionsNode)) {
            throw parseException();
        } else {
            return true;
        }
    }

    private boolean or(SyntaxNode conditionsNode) {
        if(!and(conditionsNode)) {
            throw parseException();
        } else {
            return orTail(conditionsNode);
        }
    }

    private boolean orTail(SyntaxNode parentNode) {
        if(this.getSyntaxType() == SyntaxType.Or) {
            SyntaxNode orNode = new SyntaxNode(SyntaxNodeType.Or);
            parentNode.addChild(orNode);
            nextToken();
            if(!and(parentNode)) {
                throw parseException();
            } else {
                return orTail(parentNode);
            }
        } else {
            return true;
        }
    }

    private boolean and(SyntaxNode parentNode) {
        SyntaxNode conditionsNode = new SyntaxNode(SyntaxNodeType.Conditions);
        parentNode.addChild(conditionsNode);
        if(!bracketOrLeftOpRight(conditionsNode)) {
            throw parseException();
        } else {
            return andTail(conditionsNode);
        }
    }

    private boolean andTail(SyntaxNode conditionsNode) {
        if(this.getSyntaxType() == SyntaxType.And) {
            SyntaxNode andNode = new SyntaxNode(SyntaxNodeType.And);
            conditionsNode.addChild(andNode);
            nextToken();
            if(!bracketOrLeftOpRight(conditionsNode)) {
                throw parseException();
            } else {
                return andTail(conditionsNode);
            }
        } else {
            return true;
        }
    }

    private boolean bracketOrLeftOpRight(SyntaxNode conditionsNode) {
        if(this.getSyntaxType() == SyntaxType.BracketStart) {
            nextToken();
            if(!or(conditionsNode)) {
                throw parseException();
            } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                nextToken();
                return true;
            } else {
                throw parseException();
            }
        } else {
            SyntaxNode conditionNode = new SyntaxNode(SyntaxNodeType.Condition);
            conditionsNode.addChild(conditionNode);
            return leftOpRight(conditionNode);
        }
    }

    private boolean leftOpRight(SyntaxNode conditionsNode) {
        if(this.getSyntaxType() == SyntaxType.Identifier) {
            conditionsNode.addChild(new SyntaxNode(SyntaxNodeType.Identifier, this.getStrToken()));
            nextToken();
            if(this.getSyntaxType() == SyntaxType.Op || this.getSyntaxType() == SyntaxType.In) {
                conditionsNode.addChild(new SyntaxNode(SyntaxNodeType.Op, this.getStrToken()));
                nextToken();
                if(isElement(this.getSyntaxType())) {
                    if(this.getSyntaxType() == SyntaxType.Number) {
                        conditionsNode.addChild(new SyntaxNode(SyntaxNodeType.Number, Utils.parseNumber(this.getStrToken())));
                    } else if(this.getSyntaxType() == SyntaxType.String) {
                        conditionsNode.addChild(new SyntaxNode(SyntaxNodeType.String, this.getStrToken()));
                    } else if(this.getSyntaxType() == SyntaxType.Identifier) {
                        conditionsNode.addChild(new SyntaxNode(SyntaxNodeType.Identifier, this.getStrToken()));
                    } else {
                        throw parseException();
                    }
                    nextToken();
                    return true;
                } else if(this.getSyntaxType() == SyntaxType.BracketStart){
                    nextToken();
                    SyntaxNode sqlNode = new SyntaxNode(SyntaxNodeType.ConditionItem, null);
                    conditionsNode.addChild(sqlNode);
                    if(this.getSyntaxType() == SyntaxType.Select) {
                        if(!sql(sqlNode)) {
                            throw parseException();
                        } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                            nextToken();
                            return true;
                        } else {
                            throw parseException();
                        }
                    } else {
                        if(!inCondition(sqlNode)) {
                            throw parseException();
                        } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                            nextToken();
                            return true;
                        } else {
                            throw parseException();
                        }
                    }
                } else {
                    throw parseException();
                }
            } else {
                throw parseException();
            }
        } else {
            throw parseException();
        }
    }

    private boolean inCondition(SyntaxNode parentNode) {
        while (true) {
            if(isElement(this.getSyntaxType())) {
                if(this.getSyntaxType() == SyntaxType.Number) {
                    parentNode.addChild(new SyntaxNode(SyntaxNodeType.Number, Utils.parseNumber(this.getStrToken())));
                } else if(this.getSyntaxType() == SyntaxType.String) {
                    parentNode.addChild(new SyntaxNode(SyntaxNodeType.String, this.getStrToken()));
                } else if(this.getSyntaxType() == SyntaxType.Identifier) {
                    parentNode.addChild(new SyntaxNode(SyntaxNodeType.Identifier, this.getStrToken()));
                } else {
                    throw parseException();
                }
                nextToken();
                if(this.getSyntaxType() == SyntaxType.Comma) {
                    nextToken();
                } else if(this.getSyntaxType() == SyntaxType.BracketEnd) {
                    break;
                } else {
                    throw parseException();
                }
            } else {
                throw parseException();
            }
        }
        return true;
    }


    //</editor-fold>

}
